﻿using System;
using System.Collections.Generic;
using System.IO;
using Castle.DynamicProxy;
using HardMock.Domain;
using HardMock.Recording;

namespace HardMock
{
    /// <summary>
    /// TODO : See Windsor for caching in order to avoid serialization during recordKey generation
    /// </summary>
    public sealed class StateLessHardMock : IHardMock
    {
        private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();

        private readonly IEnumerable<Type> excludedTypes;

        public StateLessHardMock(params Type[] excludedTypes)
        {
            this.excludedTypes = excludedTypes;
        }

        public T BuildRecorderOver<T>(T target) where T : class
        {
            return BuildRecorderOver(target, CreateDefaultIdentifierFactory());
        }

        internal T BuildRecorderOver<T>(T target, IIdentifierFactory identifierFactory) where T : class
        {
            Preconditions<T>();

            var recorderInterceptor = new RecorderAspect(new InMemoryRepository(new BinarySerializer()), identifierFactory, CreateDefaultMethodFactory());
            return ProxyGenerator.CreateInterfaceProxyWithTarget(target, recorderInterceptor);
        }

        public T BuildMockFrom<T>(Stream recordedCallsBin) where T : class
        {
            return BuildMockFrom<T>(recordedCallsBin, CreateDefaultIdentifierFactory());
        }

        internal T BuildMockFrom<T>(Stream recordedCallsBin, IIdentifierFactory identifierFactory) where T : class
        {
            Preconditions<T>();

            var replayerInterceptor = new ReplayerAspect(new InMemoryRepository(new BinarySerializer(), recordedCallsBin), identifierFactory, CreateDefaultMethodFactory());
            return ProxyGenerator.CreateInterfaceProxyWithoutTarget<T>(replayerInterceptor);
        }

        private IMethodCallFactory CreateDefaultMethodFactory()
        {
            return new FilteredMethodCallFactory(new MethodCallFactory(), excludedTypes);
        }

        private static void Preconditions<T>()
        {
            var targetType = typeof(T);
            var targetTypeFullName = targetType.FullName;

            if (!targetType.IsInterface)
            {
                throw new HardMockException(string.Format("type {0} should be an interface", targetTypeFullName));
            }

            if (!targetType.IsVisible)
            {
                throw new HardMockException(string.Format("type {0} should be public", targetTypeFullName));
            }
        }

        private static IIdentifierFactory CreateDefaultIdentifierFactory()
        {
            return new PropertyCallIdentifierFactory(new IdentifierFactory());
        }
    }
}